﻿using System;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.IO.Pipes;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using LibMSNP.MSNPProtocol;

namespace LibMSNP.Client
{
    /// <summary>
    /// MSNPClient is the work horse.
    /// It has seperate read and write thread, 
    /// handles synchronizng the read and write thread,
    /// and notification of the GUI thread if it needs notification.
    /// </summary>
    public class MSNPClient : IDisposable
    {
        /// <summary>
        /// Require thread synchronization 
        /// </summary>
        private MSNPClientStateMap _state;
        private Queue<IMSNPCommand> _writeCommandQueue;
        private Queue<IMSNPCommand> _readCommandQueue;
        private TcpClient _commSocket;
        private Thread _writeThread;
        private Thread _readThread;        
        private int _currentTrId;

        /// <summary>
        /// DO NOT require thread synchronization
        /// </summary>
        private Mutex _threadMutex;
        private string _username;
        private string _password;
        private bool _closed;
        private int _timeout;

        public delegate void MSNPCommandReceived(object sender, MSNPEventArgs args);
        public delegate void MSNPServerDisconnected(object sender, MSNPDisconnectArgs args);
        public delegate void MSNPClientConnected(object sender, MSNPEventArgs args);

        public event MSNPCommandReceived OnCommandReceived;        
        public event MSNPServerDisconnected OnServerDisconnect;
        public event MSNPClientConnected OnConnectionEstablished;

        public MSNPClient(int timeout)
        {
            _timeout = timeout;
            _closed = false;
            _threadMutex = new Mutex();
            InitializeClient();
        }

        private void InitializeClient()
        {
            _state = new MSNPClientStateMap();
            _readCommandQueue = new Queue<IMSNPCommand>();
            _writeCommandQueue = new Queue<IMSNPCommand>();
            _writeThread = new Thread(new ThreadStart(WriteConnection));
            _readThread = new Thread(new ThreadStart(ReadConnection));            
        }

        public void SendMessage(string cmd)
        {
            
        }

        public IMSNPCommand RetrieveCommand()
        {
            return null;
        }

        public void Start(string user, string password)
        {
            if (_state.CurrentState != States.DISCONNECTED)
                return;
            _commSocket = new TcpClient("messenger.hotmail.com", 1863);
            NetworkStream stream = null;
            if (_commSocket.Connected)
            {
                stream = _commSocket.GetStream();
                _writeThread.Start();
                _readThread.Start();
            }
        }

        private void WriteConnection()
        {
            IMSNPCommand temp = null;
            NetworkStream stream = null;
            while (!_closed)
            {
                switch (_state.CurrentState)
                {
                    case States.DISCONNECTED:
                        {
                            _threadMutex.WaitOne(); //critical section because of transactionID
                            _writeCommandQueue.Enqueue(new MSNPCommand("VER", _currentTrId++, parameters: new string[] { "MSNP8" }));
                            _state.NextState();
                            _threadMutex.ReleaseMutex();
                        }
                        break;
                    case States.INITIATING:
                        break;
                    case States.AUTHENTICATING:
                        break;
                    case States.LOGGED_IN:
                        break;
                }
                if (this._writeCommandQueue.Count <= 0)
                    continue;
                _threadMutex.WaitOne(); //critical section because of stream access
                temp = _writeCommandQueue.Dequeue();
                int bytecount = UTF8Encoding.UTF8.GetByteCount(temp.ToString());
                byte[] buffer = UTF8Encoding.UTF8.GetBytes(temp.ToString());
                stream = _commSocket.GetStream();
                stream.Write(buffer, 0, bytecount);
                stream.Flush();
                _threadMutex.ReleaseMutex();
            }
        }
      
        private void ReadConnection()
        {
            IMSNPCommand temp = null;
            NetworkStream stream = null;
            ArrayList tempBuffer = new ArrayList();
            int read = 0, index = 0;
            while (!_closed)
            {
                if (_commSocket.Available <= 0)
                    continue;

                _threadMutex.WaitOne();
                
                stream = _commSocket.GetStream();
                byte[] buffer = new byte[_commSocket.ReceiveBufferSize];
                //read = stream.Read(buffer, 0, buffer.Length);
                
                while (TruncatedCommand(buffer, buffer.Length))
                {
                    tempBuffer.InsertRange(index, buffer);
                    index += read;
                    read = stream.Read(buffer, 0, buffer.Length);
                }
                //tempBuffer.InsertRange(
                temp = MSNPCommand.CommandFromByte(buffer);
                
                _threadMutex.ReleaseMutex();
            }
        }

        private bool TruncatedCommand(byte[] buffer, int length)
        {            
            if (buffer[length] != '\0')
                return true;
            return false;
        }

        public void Stop()
        {
            if (_commSocket.Connected)
            {
                _commSocket.Close();
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (_commSocket != null)
                _commSocket.Close();
            if (!_closed)
                _closed = !_closed;
        }

        #endregion
    }
}
